/**
 * Copyright (c) 2009 Cycle Information Technology
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this 
 * software and associated documentation files (the "Software"), to deal in the Software 
 * without restriction, including without limitation the rights to use, copy, modify, 
 * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 
 * permit persons to whom the Software is furnished to do so, subject to the following 
 * conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all copies 
 * or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 
 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 
 * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 
 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 
 * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 * 
 * 
 * 
 */
package com.cycleit.cycleframework.controller {   
   import com.cycleit.cycleframework.command.ChangeViewCommand;
   import com.cycleit.cycleframework.event.CycleEvent;
   import com.cycleit.cycleframework.event.CycleEventDispatcher;
   import com.cycleit.cycleframework.event.ViewStateChangeEvent;
   import com.cycleit.cycleframework.exception.CycleException;
   import com.cycleit.cycleframework.interfaces.ICycleCommand;
   
   import flash.utils.Dictionary;
   import flash.utils.describeType;
   import flash.utils.getQualifiedClassName;

   /**
    * Controlador encargado de mantener la relación entre los CycleEvent y los ICycleCommand.
    * Los comandos que implementan ICycleCommand los usaremos para modificar nuestro modelo en los casos que no necesitemos 
    * comunicación con el servidor.
	* 
	*/
   public class CycleController  {
     /**
      * Diccionario de mapeos entre eventos y sus respectivos comandos.
      */ 
      protected var commands : Dictionary = new Dictionary();
	
	  public function CycleController() {
	  	this.addCommand(ViewStateChangeEvent.MODIFY_VIEW,ChangeViewCommand);
	  }
	  /**
	   * Permite añadir comandos a nuestra aplicación, pudiendo asignar más de un comando por evento.
	   *  
	   * @param commandName Nombre del comando a lanzar y que provocará la ejecución de la operación.
	   * @param commandRef Clase referencia del comando que habrá que instanciar asociado a <code>commandName</code>.
	   * @param useWeakReference Determina si se usa referenciado débil para los comandos de tal forma que puedan ser recolectados por el recolector de basura.
	   * 
	   */      
      public function addCommand( commandName : String, commandRef : Class, useWeakReference : Boolean = true ) : void {
      	if ( commandName == null ) {
            throw new CycleException( CycleException.COMMAND_NAME_NULL );
        }    
        if ( commandRef == null ) { 
            throw new CycleException( CycleException.COMMAND_REF_NULL );
        }
        if ( implementsICycleCommand( commandRef ) == false ) {
         	throw new CycleException( CycleException.COMMAND_SHOULD_IMPLEMENT_ICOMMAND, commandRef );
        } 
        if(commands[commandName] == null) {
         	commands[commandName] = [];			         	
        }
         
        var processes:Array = commands[commandName] 
        var exists:Boolean = false;
        var nCommand:Number = 0;
        while(nCommand < processes.length && !exists){          
          	if(processes[nCommand]==commandRef){
          		exists = true;
          		throw new CycleException( CycleException.COMMAND_ALREADY_REGISTERED, commandName );
          	}   
          	nCommand++;      	
         }
        
        if(!exists) {
        	if(processes.length == 0){
        		CycleEventDispatcher.getInstance().addEventListener( commandName, executeCommand, false, 0, useWeakReference );
        	}
        	processes[processes.length] = commandRef;
        }
      }
      
	  /**
	   * Permite eliminar un comando del controlador.
	   *  
	   * @param commandName Nombre del comando a eliminar.
	   * @param commandRef Nombre de la clase asociada al comando a eliminar.
	   * 
	   * @throws com.cycleit.cycleframework.exception.CycleException Cuando el nombre del comando buscado es igual a <code>null</code>.
	   * @throws com.cycleit.cycleframework.exception.CycleException Cuando el comando solicitado no está registrado en este controlador.
	   * 
	   */	  
	  public function removeCommand( commandName : String, commandRef:String) : void {
      	
         if ( commandName  === null ){
        	throw new CycleException( CycleException.COMMAND_NAME_NULL, commandName);  
         }
         if ( commands[ commandName ] === undefined ){
            throw new CycleException( CycleException.COMMAND_NOT_REGISTERED, commandName);  
         }
         
        
         var processes:Array = commands[commandName]; 
         for each (var ref:String in processes) {
			if(ref == commandRef){
       			processes.splice(processes.indexOf(ref),1);
       		}      	       	
         }
       
       	 if(processes.length == 0) {
       		CycleEventDispatcher.getInstance().removeEventListener( commandName, executeCommand );  	
       	 	commands[ commandName ] = undefined;
         	delete commands[ commandName ]; 
       	 }
      }
         
	  /**
	   * Ejecuta los comandos pertinentes cuando llega un evento.
	   *  
	   * @param event Evento que provoca la ejecución de un comando.
	   * 
	   */      
      protected function executeCommand( event : CycleEvent ) : void  {
      	 var commandsToInitialise : Array = getCommand( event.type );
      	 var commandCount:uint = commandsToInitialise.length;
      	 for(var i:uint=0; i<commandCount; i++){
      	 	var command:Class = (commandsToInitialise[i] as Class);
      	 	var commandToExecute : ICycleCommand = new command();
      	 	commandToExecute.execute( event );      	 	
      	 }
      	          
      }
      
	  /**
	   * Devuelve la lista de comandos para un identificador del diccionario.
	   *  
	   * @param commandName
	   * @return Lista de comandos asociados a un nombre.
	   * 
	   * @throws com.cycleit.cycleframework.exception.CycleException Cuando el comando solicitado no se encuentra registrado en el controlador.
	   */
      protected function getCommand( commandName : String ) : Array {
         var commands : Array = commands[ commandName ];
         
         if ( commands == null ){
            throw new CycleException( CycleException.COMMAND_NOT_FOUND, commandName );
         }   
         return commands;
      }      
      
      private function implementsICycleCommand( commandRef : Class ) : Boolean {
         var classDescription : XML = describeType( commandRef ) as XML;
         
         return classDescription.factory.implementsInterface.( @type == getQualifiedClassName(ICycleCommand ) ).length() != 0;
      }
   }   
}
